#include "StdAfx.h"

SerialPort::SerialPort(wstring LinkName, PnPControl *ptr)
{
    Name = LinkName;
    Notify = NULL;
    NotificationPair.first = NULL;
    NotificationPair.second = NULL;
    Reference = 0;
    Handle = CreateFile(Name.c_str(),
                        GENERIC_WRITE | GENERIC_READ,
                        0,
                        NULL,
                        OPEN_EXISTING,
                        FILE_ATTRIBUTE_NORMAL,
                        NULL);
    if (Handle == INVALID_HANDLE_VALUE)
    {
        return;
    }
    ULONG ulReturnedLength = 0;
    BYTE buf[512];
    PVIRTIO_PORT_INFO inf = (PVIRTIO_PORT_INFO)buf;
    BOOL res = DeviceIoControl(Handle, IOCTL_GET_INFORMATION, NULL, 0, buf, sizeof(buf), &ulReturnedLength, NULL);
    if (res == FALSE)
    {
        printf("Error. DeviceIoControl failed %d.\n", GetLastError());
        return;
    }
    HostConnected = inf->HostConnected;
    string s = inf->Name;

    SymbolicName.resize(s.length(), L' ');
    copy(s.begin(), s.end(), SymbolicName.begin());

    Control = ptr;
    CloseHandle(Handle);
    Handle = INVALID_HANDLE_VALUE;
}
SerialPort::~SerialPort()
{
    ClosePort();
}
void SerialPort::AddRef()
{
    InterlockedIncrement(&Reference);
}
void SerialPort::Release()
{
    if (InterlockedDecrement(&Reference) == 0)
    {
        delete this;
    }
}
BOOL SerialPort::OpenPort()
{
    Handle = CreateFile(Name.c_str(), GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
    if (Handle == INVALID_HANDLE_VALUE)
    {
        return FALSE;
    }
    ULONG ulReturnedLength = 0;
    BYTE buf[512];
    PVIRTIO_PORT_INFO inf = (PVIRTIO_PORT_INFO)buf;
    BOOL res = DeviceIoControl(Handle, IOCTL_GET_INFORMATION, NULL, 0, buf, sizeof(buf), &ulReturnedLength, NULL);
    if (res == FALSE)
    {
        printf("Error. DeviceIoControl failed %d.\n", GetLastError());
        return FALSE;
    }
    HostConnected = inf->HostConnected;
    Notify = Control->RegisterHandleNotify(Handle);
    return TRUE;
}
void SerialPort::ClosePort()
{
    if (Notify != NULL)
    {
        UnregisterDeviceNotification(Notify);
        Notify = NULL;
    }
    if (Handle != INVALID_HANDLE_VALUE)
    {
        CloseHandle(Handle);
        Handle = INVALID_HANDLE_VALUE;
    }
}

BOOL SerialPort::ReadPort(PVOID buf, size_t *len)
{
    BOOL res = FALSE;
    DWORD ret;
    DWORD bytes = (DWORD)(*len);
    OVERLAPPED ol = {0};

    if (buf == NULL || *len == 0)
    {
        *len = 0;
        return TRUE;
    }

    ol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (ol.hEvent == NULL)
    {
        return FALSE;
    }

    res = ReadFile(Handle, buf, bytes, &ret, &ol);
    if (!res)
    {
        DWORD err = GetLastError();
        if (err != ERROR_IO_PENDING)
        {
            printf("Read failed but isn't delayed. Error = %d\n", err);
            res = FALSE;
        }
        else
        {
            if (!GetOverlappedResult(Handle, &ol, &ret, TRUE))
            {
                res = FALSE;
            }
            else
            {
                *len = ret;
                res = TRUE;
            }
        }
    }
    else
    {
        *len = ret;
        res = TRUE;
    }

    CloseHandle(ol.hEvent);
    return res;
}

BOOL SerialPort::WritePort(PVOID buf, size_t *len)
{
    BOOL res = FALSE;
    ULONG ret = 0;
    DWORD bytes = (DWORD)(*len);
    OVERLAPPED ol = {0};

    if (buf == NULL || *len == 0)
    {
        *len = 0;
        return TRUE;
    }

    ol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

    if (ol.hEvent == NULL)
    {
        return FALSE;
    }

    res = WriteFile(Handle, buf, bytes, &ret, &ol);
    if (!res)
    {
        DWORD err = GetLastError();
        if (err != ERROR_IO_PENDING)
        {
            printf("Write failed but isn't delayed. Error = %d\n", err);
            res = FALSE;
        }
        else
        {
            if (!GetOverlappedResult(Handle, &ol, &ret, TRUE))
            {
                res = FALSE;
            }
            else
            {
                *len = ret;
                res = TRUE;
            }
        }
    }
    else
    {
        *len = ret;
        res = TRUE;
    }

    CloseHandle(ol.hEvent);
    return res;
}

void SerialPort::handleEvent(const PnPControl &ref)
{
    PnPNotification Notification = ref.GetNotification();
    switch (Notification.wParam)
    {
        case DBT_CUSTOMEVENT:
            {
                PDEV_BROADCAST_HANDLE pHdr;
                pHdr = (PDEV_BROADCAST_HANDLE)Notification.lParam;
                if (IsEqualGUID(GUID_VIOSERIAL_PORT_CHANGE_STATUS, pHdr->dbch_eventguid) &&
                    (pHdr->dbch_handle == Handle))
                {
                    PVIRTIO_PORT_STATUS_CHANGE pEventInfo = (PVIRTIO_PORT_STATUS_CHANGE)pHdr->dbch_data;
                    HostConnected = pEventInfo->Reason;
                    if (NotificationPair.first)
                    {
                        NotificationPair.first(NotificationPair.second);
                    }
                }
            }
            break;
    }
}
